package com.mmall.seckill.service.impl;

import com.mmall.seckill.common.CodeMsg;
import com.mmall.seckill.common.Const;
import com.mmall.seckill.common.Result;
import com.mmall.seckill.common.redis.RedisService;
import com.mmall.seckill.common.redis.keyCode.GoodsKey;
import com.mmall.seckill.common.redis.keyCode.OrderKey;
import com.mmall.seckill.common.redis.keyCode.SeckillKey;
import com.mmall.seckill.dao.GoodsDao;
import com.mmall.seckill.dao.OrderDao;
import com.mmall.seckill.domain.*;
import com.mmall.seckill.rabbitMQ.miaosha.MiaoshaMessage;
import com.mmall.seckill.rabbitMQ.miaosha.send.SendMiaoshaMessage;
import com.mmall.seckill.service.GoodsService;
import com.mmall.seckill.service.OrderService;
import com.mmall.seckill.service.SeckillService;
import com.mmall.seckill.util.JsonUtil;
import com.mmall.seckill.util.MD5Util;
import com.mmall.seckill.util.UUIDUtil;
import com.mmall.seckill.vo.GoodsVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;



/**
 * Created by megan on 2018/6/19.
 * 秒杀核心功能业务
 */
@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    @Resource
    private GoodsService goodsService;
    @Resource
    private OrderDao orderDao;
    @Resource
    private GoodsDao goodsDao;
    @Resource
    private OrderService orderService;
    @Resource
    private RedisService redisService;
    @Resource
    private SendMiaoshaMessage sendMiaoshaMessage;




    /**
     * 秒杀流程
     * v1.0 QPS -> 1400 :   解决超卖：                数据库增加 stock_count > 0
     *          解决1个用户同时秒杀多个商品 ：    建立数据库唯一索引，数据库报错，代码回滚。
     *          小优化：                       下订单的时候，把订单信息写入缓存 getMiaoshaOrderByUserIdGoodsId 增加qps；
     *
     * @param goodsId
     * @param user
     * @return
     */
    @Override
    public Result miaosha(Long goodsId, SeckillUser user){
        GoodsVo goods = goodsDao.getGoodsVoByGoodsId(goodsId);
        int stock = goods.getStockCount();
        //判断库存
        if(stock <= 0){
            return Result.coderMsg(CodeMsg.MIAO_SHA_OVER);
        }
        // 查询缓存是否有订单
        OrderInfo orderInfoCache = getMiaoshaOrderByUserIdGoodsId(user.getId(),goodsId);
        // 判断是否重复秒杀
        if(orderInfoCache != null){
            return Result.coderMsg(CodeMsg.REPEATE_MIAOSHA);
        }
        // 减库存,下订单,写入秒杀订单
        OrderInfo orderInfo = orderService.miaoshaDb(user,goods);
        if(orderInfo != null){
            return Result.success(orderInfo);
        }
        return Result.coderMsg(CodeMsg.MIAO_SHA_OVER);
    }

    /**
     * v2.0 秒杀接口:
     *           思路：减少数据库访问。
     *           1,系统初始化，把商品库存数量加载到redis中。
     *           2,收到请求，redis预减库存，库存不足，直接返回，否则进入3
     *           3，请求入队，立即返回排队中。
     *           4，请求出对，生成订单，减少库存。
     *           5，客户端轮询，是否秒杀成功。
     * @param goodsId
     * @param user
     * @return
     */
    @Override
    public Result miaoshaV2(Long goodsId, SeckillUser user){
        // 使用内存标记减少redis访问
        Boolean over = Const.localOverMap.get(goodsId);
        if(over != null && over){
            return Result.coderMsg(CodeMsg.MIAO_SHA_OVER);
        }
        // 减redis库存  有可能redis减库存减到负数。。。 所以预加载库存的时候需要 * 2倍 数量库存。。。
        long stock = redisService.decr(GoodsKey.getGoodsStock,goodsId+"_");
        if(stock > 0){
            // 判断是否重复秒杀
            OrderInfo orderInfo = getMiaoshaOrderByUserIdGoodsId(user.getId(),goodsId);
            if(orderInfo == null){
                log.info("入队，秒杀中 等待...");
                MiaoshaMessage miaoshaMessage = new MiaoshaMessage();
                miaoshaMessage.setUser(user);
                miaoshaMessage.setGoodsId(goodsId);
                sendMiaoshaMessage.sendSeckill(miaoshaMessage);
                return Result.coderMsg(CodeMsg.AWAIT_MIAOSHA);
            }
            log.info("缓存中有订单信息 -> 秒杀失败");
            return Result.coderMsg(CodeMsg.REPEATE_MIAOSHA);
        }
        Const.localOverMap.put(goodsId,true);
        return Result.coderMsg(CodeMsg.MIAO_SHA_OVER);
    }

    /**
     * 轮询是否秒杀成功
     * @param userId
     * @param goodsId
     * @return -1 -> 秒杀失败 。 0 -> 排队中。 订单编号 -> 秒杀成功
     */
    @Override
    public Long getMiaoshaResult(Long userId,Long goodsId){
        if(userId != null && goodsId != null){
            Boolean f = JsonUtil.string2Obj(redisService.get(SeckillKey.isGoodsOver,goodsId+"_"),Boolean.class);
            if(f != null && f){
                log.info("库存不足,重复秒杀等因素 --> 秒杀失败");
                return -1L;
            }
            // 读缓存数据
            OrderInfo orderInfo = getMiaoshaOrderByUserIdGoodsId(userId,goodsId);
            if(orderInfo == null){
                SeckillOrder order = orderDao.getMiaoshaOrderByUserIdGoodsId(userId,goodsId);
                // 没有查到订单，继续轮询。。
                if(order != null){
                    log.info("秒杀失败，重复秒杀 userId -> {} , goodsId ->{}",userId,goodsId);
                    return -1L;
                }
                return 0L;
            }
            log.info("真正秒杀到了");
            return orderInfo.getId();
        }
        return null;
    }

    /**
     * 生成秒杀path
     * @return
     */
    @Override
    public String createMiaoshaPath(SeckillUser user, Long goodsId) {
        String str = MD5Util.md5(UUIDUtil.uuid()+ Const.SeckillPath);
        redisService.setEx(SeckillKey.getSeckillPath,goodsId+"_"+user.getId(),str);
        return str;
    }

    /**
     * 校验秒杀地址信息
     * @return
     */
    @Override
    public Boolean checkPath(SeckillUser user, Long goodsId,String path) {
        if(user != null && goodsId != null && StringUtils.isNoneBlank(path)){
            String pathOld = redisService.get(SeckillKey.getSeckillPath,goodsId+"_"+user.getId());
            if(StringUtils.isNoneBlank(pathOld)){
                if(StringUtils.equals(path,pathOld)){
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 生成图片验证码
     * @param user
     * @param goodsId
     * @return
     */
    @Override
    public BufferedImage createMiaoshaVerifyCode(SeckillUser user, Long goodsId){
        if(user != null && goodsId != null){
            int width = 100;
            int height = 32;
            //create the image
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = image.getGraphics();
            // set the background color
            g.setColor(new Color(0xDCDCDC));
            g.fillRect(0, 0, width, height);
            // draw the border
            g.setColor(Color.black);
            g.drawRect(0, 0, width - 1, height - 1);
            // create a random instance to generate the codes
            Random rdm = new Random();
            // make some confusion
            for (int i = 0; i < 50; i++) {
                int x = rdm.nextInt(width);
                int y = rdm.nextInt(height);
                g.drawOval(x, y, 0, 0);
            }
            // generate a random code
            String verifyCode = generateVerifyCode(rdm);
            g.setColor(new Color(0, 100, 0));
            g.setFont(new Font("Candara", Font.BOLD, 24));
            g.drawString(verifyCode, 8, 24);
            g.dispose();
            //把验证码存到redis中
            Integer rnd = calc(verifyCode);
            redisService.setEx(SeckillKey.getSeckillVerifyCode, user.getId()+"_"+goodsId, rnd.toString());
            //输出图片
            return image;
        }
        return null;
    }

    private OrderInfo getMiaoshaOrderByUserIdGoodsId(Long userId , Long goodsId){
        OrderInfo order = JsonUtil.string2Obj(redisService.get(OrderKey.getSeckillOrderByYidGid,userId+"_"+goodsId),OrderInfo.class);
        return order;
    }

    private static Integer calc(String exp) {
        try {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("JavaScript");
            return (Integer)engine.eval(exp);
        }catch(Exception e) {
            log.error("计算验证码异常 -> {}",e);
            return 0;
        }
    }

    /**
     * + - *
     * */
    private String generateVerifyCode(Random rdm) {
        int num1 = rdm.nextInt(10);
        int num2 = rdm.nextInt(10);
        int num3 = rdm.nextInt(10);
        char op1 = ops[rdm.nextInt(3)];
        char op2 = ops[rdm.nextInt(3)];
        String exp = ""+ num1 + op1 + num2 + op2 + num3;
        return exp;
    }
    private static char[] ops = new char[] {'+', '-', '*'};
}
